
#include <apr_date.h>
#include "../net/netgeneral.h"
#include "../utils/StringUtils.h"
#include "../net/Broker4.h"
#include "../iface/APRInterface.h"
#include "ActorsDB.h"

// All deletes are going to have to be in DBArbitrator

ActorsDB::~ActorsDB()
{
}

std::vector<char *> *ActorsDB::GetUserWorkDataState(const char *name, const char *work_store_name, apr_pool_t *mp)
{
   if ((name == NULL) || (strlen(name) == 0))
   {
      errprint("cannot get work store state with %s user name", (name) ? "an empty" : "a NULL");
      return NULL;
   }
   if ((work_store_name == NULL) || (strlen(work_store_name) == 0))
   {
      errprint("cannot get work store state with %s work store name", (work_store_name) ? "an empty" : "a NULL");
      return NULL;
   }
   std::vector<char *> *fs_info = SQLScanAllTextf("select name from work_stores where owner='%s' and name='%s' and started='Y';", name, work_store_name);
   if ((fs_info == NULL) || (fs_info->size() == 0))
   {
      if (fs_info) delete fs_info;
      fs_info = NULL;
      return NULL;
   }
#define THESE_WKITEMS 7
   std::vector<char *> *ret_stringlist = new std::vector<char *>();
   for (int i = 0; i < (int)(fs_info->size()); i++)
   {
      std::vector<char *> *wk_info =
         SQLScanAllTextf("select name,rfc822date,active,tmax,lasthappydate,blocksuploaded,jobclusterid from workdata where file_store='%s';", fs_info->at(i));
      int total_workdatas = (int)(wk_info->size()) / THESE_WKITEMS;
      char *stage_indicate = (char *)malloc(sizeof(char)*total_workdatas);
      int *stage_pthou = (int *)malloc(sizeof(int)*total_workdatas);
      for (int j = 0; j < total_workdatas; j++)
      {
         std::vector<char *> *wk_dstore_info2 =
            SQLScanRowTextf("select name,blocks,started from work_stores where originated_from='%s';", wk_info->at(j*THESE_WKITEMS + 0));
         int indicator = 0;
         if ((wk_dstore_info2 == NULL) || (wk_dstore_info2->size() == 0))
         {
            if (wk_dstore_info2) delete wk_dstore_info2;
         } else
         {
            if (wk_dstore_info2->at(2)[0] == 'U')
            {
               indicator = 1;
            }
            StringUtils::DestroyStringList(wk_dstore_info2);
         }
         if (wk_info->at(j*THESE_WKITEMS + 2)[0] == 'Y') indicator = 1;
         stage_indicate[j] = 'B';
         if (indicator)
         {
            // work data is active
            stage_indicate[j] = 'C';
            if (strlen(wk_info->at(j*THESE_WKITEMS + 6)) > 0)
            {
               // received a jobclusterid
               stage_indicate[j] = 'D';
               if (atoi(wk_info->at(j*THESE_WKITEMS + 5)) > 0)
               {
                  // have blocks uploaded
                  stage_indicate[j] = 'E';
                  if (strlen(wk_info->at(j*THESE_WKITEMS + 4)) > 0)
                  {
                     // there exists a last happy date
                     stage_indicate[j] = 'F';
                     std::vector<char *> *wk_dstore_info =
                        SQLScanRowTextf("select name,blocks,started from work_stores where originated_from='%s';", wk_info->at(j*THESE_WKITEMS + 0));
                     if ((wk_dstore_info == NULL) || (wk_dstore_info->size() == 0))
                     {
                        if (wk_dstore_info) delete wk_dstore_info;
                     } else
                     {
                        // have a work_store attached to the work_data (compute complete)
                        stage_indicate[j] = 'G';
                        if (wk_dstore_info->at(2)[0] == 'U')
                        {
                           stage_indicate[j] = 'H';
                        }
                        StringUtils::DestroyStringList(wk_dstore_info);
                     }
                  }
               }
            }
         }
      }
      for (int j = 0; j < total_workdatas; j++)
      {
         switch (stage_indicate[j])
         {
            case 'E':
            {
               // count the number of blocksuploaded sent to the compute against the work_store count and make a pecentage
               int total_blocks = 0;
               SQLGetSingleIntf(&total_blocks, "select blocks from work_stores where name='%s';", fs_info->at(i));
               int currblock = atoi(wk_info->at(j*THESE_WKITEMS + 5));
               stage_pthou[j] = (total_blocks > 0) ? ((1000 * currblock) / (total_blocks)) : 0;
               break;
            }
            case 'F':
            {
               // last happy date, so compute is running!
               stage_pthou[j] = 1;
               // I have no idea what this should be!
               break;
            }
            case 'G':
            {
               // check the set of files, the count of the complete ones gives the percentage
               int total_blocks = 0;
               SQLGetSingleIntf(&total_blocks, "select blocks from work_stores where originated_from='%s';", wk_info->at(j*THESE_WKITEMS + 0));
               if (total_blocks == 0)
               {
                  stage_pthou[j] = 0;
                  break;
               }
               char *wsname = NULL;
               SQLGetSingleStringf(&wsname, "select name from work_stores where originated_from='%s';", wk_info->at(j*THESE_WKITEMS + 0));
               if (wsname == NULL)
               {
                  stage_pthou[j] = 0;
                  break;
               }
               int cumulative_complete = 0;
               for (int k = 0; k < total_blocks; k++)
               {
                  char *filename_c = StringUtils::varprintf(ACQUIRE_DYNAMIC_DIR "/" "_%s.c%d", wsname, k);
                  apr_size_t fsize_c = Broker4::GetFileSize(filename_c, mp);
                  free(filename_c);
                  char *filename_f = StringUtils::varprintf(ACQUIRE_DYNAMIC_DIR "/" "_%s.f%d", wsname, k);
                  apr_size_t fsize_f = Broker4::GetFileSize(filename_f, mp);
                  free(filename_f);
                  cumulative_complete += (fsize_f > fsize_c);
               }
               stage_pthou[j] = (1000*cumulative_complete) / total_blocks;
               if (wsname) free(wsname);
               break;
            }
            default:
            {
               // set a 0 percentage
               stage_pthou[j] = 0;
               break;
            }
         }
         // so maybe "file_store workdata.name stage_indicate stage_pthou" per line?
         char *nstring = StringUtils::varprintf("%s: %c-%d", wk_info->at(j*THESE_WKITEMS + 0), stage_indicate[j], stage_pthou[j]);
         ret_stringlist->push_back(nstring);
      }
      StringUtils::DestroyStringList(wk_info);
      free(stage_indicate);
      free(stage_pthou);
   }
   return ret_stringlist;
}

std::vector<char *> *ActorsDB::GetUserState(const char *name, apr_pool_t *mp)
{
   if ((name == NULL) || (strlen(name) == 0))
   {
      errprint("cannot get user state with %s user name", (name) ? "an empty" : "a NULL");
      return NULL;
   }
   // 0 - ready to upload/uploading
   // 1 - uploaded/ready to run
   //printf("Query for job information about %s\n", name);
   std::vector<char *> *fs_info = SQLScanAllTextf("select name from work_stores where owner = '%s';", name);
   if ((fs_info == NULL) || (fs_info->size() == 0))
   {
      if (fs_info) delete fs_info;
      fs_info = NULL;
      return NULL;
   }
   int *stages = (int *)malloc(sizeof(int)*(fs_info->size()));
   int *perthou = (int *)malloc(sizeof(int)*(fs_info->size()));
   int *perthou_2 = (int *)malloc(sizeof(int)*(fs_info->size()));
   for (int i = 0; i < (int)(fs_info->size()); i++) { stages[i] = 0; perthou[i] = 0; perthou_2[i] = 0; }
   std::vector<char *> *fs_info2 = SQLScanAllTextf("select name from work_stores where owner = '%s' and started = 'Y';", name);
   // if running then there should be a 'Y' on the jobs work active thing
   if ((fs_info2 == NULL) || (fs_info2->size() == 0))
   {
      if (fs_info2) delete fs_info2;
      fs_info2 = NULL;
   } else
   {
      for (int i = 0; i < (int)(fs_info->size()); i++)
         for (int j = 0; j < (int)(fs_info2->size()); j++)
         {
 //           printf("(%s,%s)-%d\n", fs_info->at(i), fs_info2->at(j), strcmp(fs_info->at(i), fs_info2->at(j)));
            stages[i] += (strcmp(fs_info->at(i), fs_info2->at(j)) == 0);
         }
      StringUtils::DestroyStringList(fs_info2);
   }
   // if not, then we should be able to find a percentage for their upload completion
   for (int i = 0; i < (int)(fs_info->size()); i++)
      if (stages[i] == 0)
      {
         std::vector<char *> *bn_info = SQLScanRowTextf("select blocks from work_stores where name='%s';", fs_info->at(i));
         if ((bn_info == NULL) || (bn_info->size() == 0))
         {
            if (bn_info) delete bn_info;
         } else
         {
            int cumulative_complete = 0;
            for (int j = 0; j < atoi(bn_info->at(0)); j++)
            {
               char *filename_c = StringUtils::varprintf(ACQUIRE_DYNAMIC_DIR "/" "_%s.c%d", fs_info->at(i), j);
               apr_size_t fsize_c = Broker4::GetFileSize(filename_c, mp);
               free(filename_c);
               char *filename_f = StringUtils::varprintf(ACQUIRE_DYNAMIC_DIR "/" "_%s.f%d", fs_info->at(i), j);
               apr_size_t fsize_f = Broker4::GetFileSize(filename_f, mp);
               free(filename_f);
               cumulative_complete += (fsize_f > fsize_c);
            }
            perthou[i] = (1000*cumulative_complete) / atoi(bn_info->at(0));
            StringUtils::DestroyStringList(bn_info);
         }
      }
   // so for the work_stores that do have 'Y' they are split into many workdata parts...
   for (int i = 0; i < (int)(fs_info->size()); i++)
      if (stages[i] == 1)
      {
         std::vector<char *> *wk_info = SQLScanAllTextf("select name,rfc822date,active,tmax from workdata where file_store = '%s';", fs_info->at(i));
         if ((wk_info == NULL) || (wk_info->size() == 0))
         {
            if (wk_info) delete wk_info;
            wk_info = NULL;
         } else
         {
            stages[i]++;
            int *running_comp_perthou = (int *)malloc(sizeof(int)*(wk_info->size() / 4));
            int *uploaded_comp_perthou = (int *)malloc(sizeof(int)*(wk_info->size() / 4));
            for (int k = 0; k < (int)(wk_info->size() / 4); k++)
            {
               //printf("WKINFO %s %s %s %s\n", wk_info->at(k*4+0), wk_info->at(k*4+1), wk_info->at(k*4+2), wk_info->at(k*4+3));
               running_comp_perthou[k] = 0;
               uploaded_comp_perthou[k] = 0; // ((wk_info->at(k*4 + 2))[0])
               std::vector<char *> *fs_info3 = SQLScanRowTextf("select name,blocks,started from work_stores where originated_from = '%s';", wk_info->at(k*4 + 0));
               if ((fs_info3 == NULL) || (fs_info3->size() == 0))
               {
                  if (fs_info3) delete fs_info3;
               } else
               {
                  //printf("FSINFO3 %s %s %s\n", fs_info3->at(0), fs_info3->at(1), fs_info3->at(2));
                  switch ((fs_info3->at(2))[0])
                  {
                     case 'N': errprint("File store for work result should not have an 'N' motif"); break;
                     case 'Y': errprint("File store for work result should not have an 'Y' motif"); break;
                     case 'I':
                     {
                        int cumulative_complete = 0;
                        for (int j = 0; j < atoi(fs_info3->at(1)); j++)
                        {
                           char *filename_c = StringUtils::varprintf(ACQUIRE_DYNAMIC_DIR "/" "_%s.c%d", fs_info3->at(0), j);
                           apr_size_t fsize_c = Broker4::GetFileSize(filename_c, mp);
                           free(filename_c);
                           char *filename_f = StringUtils::varprintf(ACQUIRE_DYNAMIC_DIR "/" "_%s.f%d", fs_info3->at(0), j);
                           apr_size_t fsize_f = Broker4::GetFileSize(filename_f, mp);
                           free(filename_f);
                           cumulative_complete += (fsize_f > fsize_c);
                        }
                        uploaded_comp_perthou[k] = (1000*cumulative_complete) / atoi(fs_info3->at(1));
                        char timebuf[APR_RFC822_DATE_LEN + 1];
                        apr_status_t status;
                        apr_time_t timet = apr_time_now();
                        status = apr_rfc822_date(timebuf, timet);
                        CheckAPRError(status);
                        apr_time_t timebefore = apr_date_parse_rfc(wk_info->at(k*4 + 1));
                        int wk_tlen = atoi(wk_info->at(k*4 + 3));
                        if (wk_tlen == 0)
                        {
                           errprint("The tmax of this work '%s' is zero?", fs_info3->at(0));
                           wk_tlen = 1;
                        }
                        running_comp_perthou[k] = (int)((apr_time_sec(timet - timebefore)*1000) / wk_tlen);
                        break;
                     }
                     case 'U':
                     {
                        running_comp_perthou[k] = 1000;
                        uploaded_comp_perthou[k] = 1000;
                        break;
                     }
                  }
                  StringUtils::DestroyStringList(fs_info3);
               }
            }
            perthou[i] = 0; perthou_2[i] = 0;
            for (int k = 0; k < (int)(wk_info->size() / 4); k++)
            {
               perthou[i] += running_comp_perthou[k];
               perthou_2[i] += uploaded_comp_perthou[k];
            }
            free(running_comp_perthou);
            free(uploaded_comp_perthou);
            perthou[i] /= (wk_info->size() / 4);
            perthou_2[i] /= (wk_info->size() / 4);
            if (perthou[i] > 1000) perthou[i] = 1000;
            if (perthou_2[i] > 1000) perthou_2[i] = 1000;
            StringUtils::DestroyStringList(wk_info);
         }
      }
   std::vector<char *> *hlist = new std::vector<char *>();
   for (int i = 0; i < (int)(fs_info->size()); i++)
   {
      char *fs_status = StringUtils::varprintf("%s PTHOU%d.%d.%d", fs_info->at(i),
         (stages[i] == 0) ? perthou[i] : 1000,
         (stages[i] > 0) ? perthou[i] : 0,
         (stages[i] > 0) ? perthou_2[i] : 0);
      hlist->push_back(fs_status);
   }
   StringUtils::DestroyStringList(fs_info);
   free(stages);
   free(perthou);
   free(perthou_2);
   return hlist;
}

std::vector<char *> *ActorsDB::GetReadyResults(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot get results for %s work store uuid", (uuid) ? "an empty" : "a NULL");
      return NULL;
   }
   std::vector<char *> *otherlist = NULL;
   std::vector<char *> *rs_info = SQLScanAllTextf("select name,blocks,originated_from from work_stores where owner='%s' and started='U';", uuid);
   if ((rs_info == NULL) || (rs_info->size() == 0))
   {
      if (rs_info) delete rs_info;
   } else
   {
      otherlist = new std::vector<char *>();
      for (int k = 0; k < (int)(rs_info->size() / 3); k++)
      {
         otherlist->push_back(StringUtils::varprintf("Result%d: %s" CRLF_STR, k, rs_info->at(k*3+0)));
         otherlist->push_back(StringUtils::varprintf("%s: %s" CRLF_STR, rs_info->at(k*3+0), rs_info->at(k*3+2)));
         otherlist->push_back(StringUtils::varprintf("%s: %s" CRLF_STR, rs_info->at(k*3+2), rs_info->at(k*3+1)));
      }
      StringUtils::DestroyStringList(rs_info);
      return otherlist;
   }
   return NULL;
}

void ActorsDB::AddUserToActors(const char *name, const char *date, int64_t credit, apr_size_t quotabytes)
{
   if ((name == NULL) || (strlen(name) == 0))
   {
      errprint("cannot add user with %s user name", (name) ? "an empty" : "a NULL");
      return;
   }
   if ((date == NULL) || (strlen(date) == 0))
   {
      errprint("cannot add date which is %s", (date) ? "empty" : "NULL");
      return;
   }
   // one for luck, one for sign, one for decimal point, one for null termination!
   char *creditamount = (char *)malloc(sizeof(char)*(8+1+1+1+1));
   sprintf(creditamount, "%06d.%02d", (int)(credit / 100), (int)(credit % 100));
   SQLInstantExecf("insert into users (name, rfc822date, credit, quota) values ('%s', '%s', '%s', '%" APR_SIZE_T_FMT "');",
      name, date, creditamount, quotabytes);
   // Though we can come back later and have group leaders access implemented as the owner of the group people!
   SQLInstantExecf("insert into actors (name, class, owner) values ('%s', 'USER', 'NULL');", name);
   free(creditamount);
}

void ActorsDB::AddWorkStore(const char *hash, const char *username, int blocks, int blocksize, const char *uncopyhash, const char *workdata)
{
   if ((hash == NULL) || (strlen(hash) == 0))
   {
      errprint("cannot add a work store with %s hash", (hash) ? "an empty" : "a NULL");
      return;
   }
   if ((username == NULL) || (strlen(username) == 0))
   {
      errprint("cannot add a work store owned without an owner: has %s user name", (username) ? "an empty" : "a NULL");
      return;
   }
   if ((uncopyhash == NULL) || (strlen(uncopyhash) == 0))
   {
      errprint("cannot add a work store without an xor'ed hash: this one is %s", (uncopyhash) ? "empty" : "NULL");
      return;
   }
   sqlite3_mutex_enter(dbmutex);
   SQLInstantExecf_unsafe("insert into work_stores (name, blocks, blocksize, owner, xorblockhash, started, instances, originated_from) values "
                   "('%s', '%d', '%d', '%s', '%s', '%c', '0', '%s');",
                   hash, blocks, blocksize, username, uncopyhash,
                   (workdata) ? 'I' : 'N', (workdata) ? workdata : username);
   SQLInstantExecf_unsafe("insert into actors (name, class, owner) values ('%s', 'WORKSTORE', '%s');", hash, username);
   sqlite3_mutex_leave(dbmutex);
}

void ActorsDB::AddOrReplaceDelegateKey(const char *delegate_key_uuid, const char *owner)
{
   if ((delegate_key_uuid == NULL) || (strlen(delegate_key_uuid) == 0))
   {
      errprint("cannot add or replace a delegate key when the UUID is %s", (delegate_key_uuid) ? "empty" : "NULL");
      return;
   }
   if ((owner == NULL) || (strlen(owner) == 0))
   {
      errprint("cannot add or replace a delegate without an owner: user name is %s", (owner) ? "empty" : "NULL");
      return;
   }
   sqlite3_mutex_enter(dbmutex);
   SQLInstantExecf_unsafe("delete from 'actors' where class='DELEGATE' and owner='%s';", owner);
   SQLInstantExecf_unsafe("insert into actors (name, class, owner) values ('%s', 'DELEGATE', '%s');", delegate_key_uuid, owner);
   sqlite3_mutex_leave(dbmutex);
}

int ActorsDB::IsWorkStoreStarted(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot check a work store without a UUID: UUID is %s", (uuid) ? "empty" : "NULL");
      return -1;
   }
   std::vector<char *> *wk_info = SQLScanRowTextf("select started from work_stores where name = '%s';", uuid);
   if ((wk_info == NULL) || (wk_info->size() == 0))
   {
      if (wk_info) delete wk_info;
      return -1;
   }
   int retval = -1;
   switch ((wk_info->at(0))[0])
   {
      case 'N': retval = 1; break;
      case 'I': retval = 1; break;
      case 'Y': retval = 0; break;
      case 'U': retval = 0; break;
   }
   StringUtils::DestroyStringList(wk_info);
   return retval;
}

void ActorsDB::WorkStoreSetCancelled(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot mark a work store as 'cancelled' without a UUID: UUID is %s", (uuid) ? "empty" : "NULL");
      return;
   }
   SQLInstantExecf("update 'work_stores' set started='C' where name='%s';", uuid);
}

void ActorsDB::WorkStoreSetStarted(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot mark a work store as 'started' without a UUID: UUID is %s", (uuid) ? "empty" : "NULL");
      return;
   }
   SQLInstantExecf("update 'work_stores' set started='Y' where name='%s';", uuid);
}

void ActorsDB::WorkStoreSetUsable(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot mark a work store as 'usable' without a UUID: UUID is %s", (uuid) ? "empty" : "NULL");
      return;
   }
   SQLInstantExecf("update 'work_stores' set started='U' where name='%s';", uuid);
}

std::vector<char *> *ActorsDB::ViewWorkStoresOwned(const char *owner)
{
   if ((owner == NULL) || (strlen(owner) == 0))
   {
      errprint("cannot view work stores of an owner if the owner name is %s", (owner) ? "empty" : "NULL");
      return NULL;
   }
   std::vector<char *> *fs_info = SQLScanAllTextf("select name from work_stores where owner = '%s';", owner);
   if ((fs_info == NULL) || (fs_info->size() == 0))
   {
      errprint("could not find any work stores owned by '%s'", owner);
      if (fs_info) delete fs_info;
      return NULL;
   }
   return fs_info;
}

std::vector<char *> *ActorsDB::ViewWorkStoreByUUID(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot view work store properties without a proper UUID: UUID is %s", (uuid) ? "empty" : "NULL");
      return NULL;
   }
   std::vector<char *> *fs_info = SQLScanRowTextf("select name,blocks,blocksize,owner,xorblockhash from work_stores where name = '%s';", uuid);
   if ((fs_info == NULL) || (fs_info->size() == 0))
   {
      errprint("could not find any work stores with the UUID '%s'", uuid);
      if (fs_info) delete fs_info;
      return NULL;
   }
   return fs_info;
}

int ActorsDB::GrantUUIDPermission(const char *uuid1, const char *uuid2)
{
   int retval = 1;
   if (uuid1 == uuid2) return 0;
   if (uuid1 == NULL)
   {
      errprint("UUID1 is NULL when trying to grant permissions");
      return 1;
   }
   if (uuid2 == NULL)
   {
      errprint("UUID2 is NULL when trying to grant permissions");
      return 1;
   }
   char *user1 = LookupUltimateUser(uuid1);
   char *user2 = LookupUltimateUser(uuid2);
   if (user1 == NULL)
      errprint("ultimate user(1) not found when lookup performed for '%s' (but traced '%s' to '%s')", uuid1, uuid2, user2);
   if (user2 == NULL)
      errprint("ultimate user(2) not found when lookup performed for '%s' (but traced '%s' to '%s')", uuid2, uuid1, user1);
   if (user1 && user2)
      retval = strcmp(user1, user2);
   if (user1) free(user1);
   if (user2) free(user2);
   if (retval != 0)
      errprint("permission denied, for combination '%s'/'%s'", uuid1, uuid2);
   return retval;
}

char *ActorsDB::LookupUltimateUser(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot look up ultimate owner without a proper UUID: UUID is %s", (uuid) ? "empty" : "NULL");
      return NULL;
   }
   int isuser = 0;
   char *copied_owner = (char *)malloc(sizeof(char)*(strlen(uuid)+1));
   strcpy(copied_owner, uuid);
   int maxhops = 20;
   while (maxhops--)
   {
      std::vector<char *> *acts = SQLScanRowTextf("select name,class,owner from actors where name = '%s';", copied_owner);
      if ((acts == NULL) || (acts->size() == 0))
         return NULL;
      if (strcmp(acts->at(1), "USER") == 0)
      {
         StringUtils::DestroyStringList(acts);
         return copied_owner;
      }
      free(copied_owner);
      copied_owner = (char *)malloc(sizeof(char)*(strlen(acts->at(2))+1));
      strcpy(copied_owner, acts->at(2));
      StringUtils::DestroyStringList(acts);
   }
}

int ActorsDB::ModifyUserQuota(const char *username, apr_off_t modifier)
{
   if ((username == NULL) || (strlen(username) == 0))
   {
      errprint("cannot modify an anonymous quota without an owner: has %s user name", (username) ? "an empty" : "a NULL");
      return -1;
   }
   apr_size_t currquota = (apr_size_t)GetUserQuota(username);
   return ReplaceUserQuota(username, (apr_size_t)((apr_off_t)currquota + modifier));
}

int ActorsDB::ReplaceUserQuota(const char *username, apr_size_t newquota)
{
   if ((username == NULL) || (strlen(username) == 0))
   {
      errprint("cannot replace an anonymous quota without an owner: has %s user name", (username) ? "an empty" : "a NULL");
      return -1;
   }
   return SQLInstantExecf("update users set quota = '%" APR_SIZE_T_FMT "' where name = '%s';", newquota, username);
}

apr_off_t ActorsDB::GetUserQuota(const char *username)
{
   if ((username == NULL) || (strlen(username) == 0))
   {
      errprint("cannot get the size of a quota without an owner: has %s user name", (username) ? "an empty" : "a NULL");
      return -1;
   }
   std::vector<char *> *fs_info = SQLScanRowTextf("select quota from users where name = '%s';", username);
   if ((fs_info == NULL) || (fs_info->size() == 0))
   {
      errprint("cannot find details of quota for user '%s'", username);
      if (fs_info) delete fs_info;
      return -1;
   }
   apr_off_t qsize;
   sscanf(fs_info->at(0), "%" APR_OFF_T_FMT, &qsize);
   StringUtils::DestroyStringList(fs_info);
   return qsize;
}

void ActorsDB::AddComputeSession(const char *uuid, int durationSeconds, apr_sockaddr_t *cliaddr, const char *capabilities)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot add a compute session with %s UUID", (uuid) ? "an empty" : "a NULL");
      return;
   }
   if (cliaddr == NULL)
   {
      errprint("cannot add a compute session without a NULL client");
      return;
   }
   if ((capabilities == NULL) || (strlen(capabilities) == 0))
   {
      errprint("cannot add a compute session with %s capability list", (capabilities) ? "an empty" : "a NULL");
      return;
   }
   char timebuf[APR_RFC822_DATE_LEN + 1];
   apr_status_t status;
   apr_time_t timet = apr_time_now();
   status = apr_rfc822_date(timebuf, timet);
   CheckAPRError(status);
   char *duration = StringUtils::varprintf("%d", durationSeconds);
   char *hostname;
   status = apr_getnameinfo(&hostname, cliaddr, 0);
   CheckAPRError(status);
   SQLInstantExecf("insert into compute_sessions (name, host, begindate, duration, capabilities) values ('%s', '%s', '%s', '%s', '%s');",
      uuid, hostname, timebuf, duration, capabilities);
   SQLInstantExecf("insert into actors (name, class, owner) values ('%s', '%s', '%s');", uuid, "COMPUTE", "NULL");
   free(duration);
}

std::vector<char *> *ActorsDB::AtomicGetWork(const char *realowner, int tmax_given, const char *forcejob)
{
   // This is where we should determine whether the work is suitable
   // Probably should make this more efficient - slightly.
   sqlite3_mutex_enter(dbmutex);
   int acceptable = 0;
   int offset = 0;
   std::vector<char *> *workmatch = NULL;
   const char *forcejob2 = forcejob;
   if (forcejob2)
      errprint("compute is demand job from workdata '%s'", forcejob);
   while (!acceptable)
   {
      if (realowner)
      {
         if (forcejob2)
         {
            workmatch = SQLScanRowTextf_unsafe("select name,tmax,localid,file_store,owner from 'workdata' where owner='%s' and active='N' and name='%s';", realowner, forcejob2);
         } else
         {
            workmatch = SQLScanRowTextf_unsafe("select name,tmax,localid,file_store,owner from 'workdata' where owner='%s' and active='N' limit 1 offset %d;", realowner, offset);
         }
      } else
      {
         if (forcejob2)
         {
            workmatch = SQLScanRowTextf_unsafe("select name,tmax,localid,file_store,owner from 'workdata' where active='N' and name='%s';", forcejob2);
         } else
         {
            workmatch = SQLScanRowTextf_unsafe("select name,tmax,localid,file_store,owner from 'workdata' where active='N' limit 1 offset %d;", offset);
         }
      }
      if ((workmatch == NULL) || (workmatch->size() == 0))
      {
         if (workmatch) delete workmatch;
         sqlite3_mutex_leave(dbmutex);
         return NULL;
      }
      //printf("work match name tmax: %s %s %d\n", workmatch->at(0), workmatch->at(1), tmax_given);
      acceptable = 1;
      // Not enough time -> unacceptable
      if (atoi(workmatch->at(1)) > tmax_given)
      {
         errprint("work '%s' not acceptable - needs %d, have %d seconds", workmatch->at(0), atoi(workmatch->at(1)), tmax_given);
         acceptable = 0;
      }
      if (!acceptable)
      {
         if (forcejob2)
         {
            forcejob2 = NULL;
         } else
         {
            if (workmatch) StringUtils::DestroyStringList(workmatch);
            offset++;
         }
      }
   }
   SQLInstantExecf_unsafe("update 'workdata' set active='Y' where name='%s';", workmatch->at(0));
   sqlite3_mutex_leave(dbmutex);
   char timebuf[APR_RFC822_DATE_LEN + 1];
   apr_status_t status;
   apr_time_t timet = apr_time_now();
   status = apr_rfc822_date(timebuf, timet);
   CheckAPRError(status);
   SQLInstantExecf("update 'workdata' set rfc822date='%s' where name='%s';", timebuf, workmatch->at(0));
   return workmatch;
}

void ActorsDB::SetCompletedWork(const char *workname)
{
   if ((workname == NULL) || (strlen(workname) == 0))
   {
      errprint("cannot set a complete work data with %s UUID", (workname) ? "an empty" : "a NULL");
      return;
   }
   SQLInstantExecf("update 'workdata' set active='C' where name='%s';", workname);
}

void ActorsDB::CleanExpiredWork()
{
   std::vector<char *> *wk_info = SQLScanAllTextf("select name,rfc822date,tmax from 'workdata' where active='Y';");
   if (wk_info == NULL) return;
   int active_works = wk_info->size() / 3;
   for (int i = 0; i < active_works; i++)
   {
      apr_time_t timebefore = apr_date_parse_rfc(wk_info->at(i*3 + 1));
      apr_time_t tnow = apr_time_now();
      if (apr_time_sec(tnow - timebefore) > atoi(wk_info->at(i*3 + 2)))
         SQLInstantExecf("update 'workdata' set active='N' where name='%s';", wk_info->at(i*3 + 0));
   }
   StringUtils::DestroyStringList(wk_info);
}

void ActorsDB::CreateWorkFromFileStoreID(const char *filestoreid, const char *clusterprefs, int maxDuration, int instances)
{
   if ((filestoreid == NULL) || (strlen(filestoreid) == 0))
   {
      errprint("filestore UUID is %s, so cannot create work data", (filestoreid) ? "empty" : "NULL");
      return;
   }
   std::vector<char *> *fs_info = SQLScanRowTextf("select owner from 'work_stores' where name='%s';", filestoreid);
   if ((fs_info == NULL) || (fs_info->size() == 0))
   {
      errprint("file store with UUID '%s' does not exist", filestoreid);
      if (fs_info) StringUtils::DestroyStringList(fs_info);
      return;
   }
   // Generate a new UUID for the work UUID
   // rfc822 date is now - this is the last execution attempt timestamp
   // active is N - so that it is run
   // localid is 0, (we can read the xml and spawn multiple workpackets from the same file store maybe later)
   // file_store is filestoreid
   // owner is fs_info->at(0)
   char timebuf[APR_RFC822_DATE_LEN + 1];
   apr_status_t status;
   apr_time_t timet = apr_time_now();
   status = apr_rfc822_date(timebuf, timet);
   CheckAPRError(status);
   for (int i = 0; i < instances; i++)
   {
      char *uuid_name = StringUtils::varprintf("%s_%d", filestoreid, i);
      SQLInstantExecf("insert into workdata "
         "(name, rfc822date, active, jobident, jobclusterid, jobclusterprefs, tmax, localid, file_store, owner, lasthappydate, computename, blocksuploaded) values "
         "('%s', '%s', 'N', '', '', '%s', '%d', '%d', '%s', '%s', '', '', '0');", uuid_name, timebuf, clusterprefs, maxDuration, i, filestoreid, fs_info->at(0));
      free(uuid_name);
   }
   SQLInstantExecf("update 'work_stores' set instances='%d' where name='%s';", instances, filestoreid);
   if (fs_info) StringUtils::DestroyStringList(fs_info);
}

int ActorsDB::GetInstancesFromWorkStore(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot get instance total from %s UUID", (uuid) ? "an empty" : "a NULL");
      return -1;
   }
   int insts = 0;
   SQLGetSingleIntf(&insts, "select instances from 'work_stores' where name='%s';", uuid);
   return insts;
}

int ActorsDB::QuietlyVerifyUUIDIsCompute(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot verify %s UUID is compute", (uuid) ? "an empty" : "a NULL");
      return 1;
   }
   return VerifyUUIDIsCompute(uuid, 1);
}

int ActorsDB::VerifyUUIDIsCompute(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot verify %s UUID is compute", (uuid) ? "an empty" : "a NULL");
      return 1;
   }
   return VerifyUUIDIsCompute(uuid, 0);
}

int ActorsDB::VerifyUUIDIsCompute(const char *uuid, int suppress)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot verify %s UUID is compute", (uuid) ? "an empty" : "a NULL");
      return 1;
   }
   std::vector<char *> *act_info = SQLScanAllTextf("select class from 'actors' where name='%s';", uuid);
   if ((act_info == NULL) || (act_info->size() == 0))
   {
      if (!suppress) errprint("UUID '%s' is not actor", uuid);
      if (act_info) StringUtils::DestroyStringList(act_info);
      return 1;
   }
   if (strcmp(act_info->at(0), "COMPUTE") == 0)
   {
      StringUtils::DestroyStringList(act_info);
      return 0;
   } else
   {
      if (!suppress) errprint("UUID '%s' is erroneously claiming to be compute", uuid);
      StringUtils::DestroyStringList(act_info);
      return 1;
   }
}

int ActorsDB::VerifyUUIDIsWorkStore(const char *uuid)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot verify %s UUID is a work store", (uuid) ? "an empty" : "a NULL");
      return 1;
   }
   std::vector<char *> *act_info = SQLScanAllTextf("select class from 'actors' where name='%s';", uuid);
   if ((act_info == NULL) || (act_info->size() == 0))
   {
      errprint("UUID '%s' is not actor", uuid);
      if (act_info) StringUtils::DestroyStringList(act_info);
      return 1;
   }
   if (strcmp(act_info->at(0), "WORKSTORE") == 0)
   {
      StringUtils::DestroyStringList(act_info);
      return 0;
   } else
   {
      errprint("UUID '%s' is erroneously claimed to be workstore", uuid);
      StringUtils::DestroyStringList(act_info);
      return 1;
   }
}

int ActorsDB::NumberOfWorkStoreBlocksFromUUID(const char *filestoreid)
{
   if ((filestoreid == NULL) || (strlen(filestoreid) == 0))
   {
      errprint("filestore UUID is %s, cannot get number of blocks", (filestoreid) ? "empty" : "NULL");
      return -1;
   }
   std::vector<char *> *fs_info = SQLScanRowTextf("select blocks from 'work_stores' where name='%s';", filestoreid);
   if ((fs_info == NULL) || (fs_info->size() == 0))
   {
      errprint("file store with UUID '%s' does not exist", filestoreid);
      if (fs_info) StringUtils::DestroyStringList(fs_info);
      return -1;
   }
   int noofblocks = atoi(fs_info->at(0));
   StringUtils::DestroyStringList(fs_info);
   return noofblocks;
}

int ActorsDB::AddKeyDependency(const char *uuid, const char *keyname)
{
   if ((uuid == NULL) || (strlen(uuid) == 0))
   {
      errprint("cannot add the key dependency '%s' to %s UUID", (keyname) ? keyname : "(null)", (uuid) ? "an empty" : "a NULL");
      return 0;
   }
   if ((keyname == NULL) || (strlen(keyname) == 0))
   {
      errprint("cannot add %s key name to be dependent upon the UUID '%s'", (keyname) ? "an empty" : "a NULL", uuid);
      return 0;
   }
   SQLInstantExecf("insert into 'key_dependencies' (uuidname, keyname) values ('%s', '%s');", uuid, keyname);
   return 1;
}

int ActorsDB::AddWorkStoreKeyDependency(const char *work_store, const char *sshkeyname)
{
   return AddKeyDependency(work_store, sshkeyname);
}

int ActorsDB::AddComputeKeyDependency(const char *compute, const char *keyname)
{
   return AddKeyDependency(compute, keyname);
}

void ActorsDB::CreateTablesIfNecessary()
{
   EnsureTablesAreCreated(6,
      "actors", "(name char(64) primary key not null, class char(64), owner char(64))",
      "users", "(name char(64) primary key not null, rfc822date char(32), credit numeric(8,2), quota char(64))",
      "compute_sessions", "(name char(64) primary key not null, host char(128), begindate char(32), duration decimal(10,0), capabilities char(4096))",
      "workdata", "(name char(64) primary key not null, "
         "rfc822date char(32), active char(1), jobident char(64), jobclusterid char(64), jobclusterprefs char(4096), tmax decimal(10,0), "
         "localid decimal(10,0), file_store char(64), owner char(64), lasthappydate char(32), computename char(64), blocksuploaded decimal(10,0))",
      "work_stores", "(name char(64) primary key not null, "
         "blocks decimal(10,0), blocksize decimal(10,0), owner char(64), xorblockhash char(64), "
         "started char(1), instances decimal(10,0), originated_from char(64))",
      "key_dependencies", "(uuidname char(64), keyname char(64))");
// work_stores -> originated_from should be generated_by
}
